1 module hip.font.bmfont; 2 import hip.api.data.font; 3 import hip.api.renderer.texture; 4 import hip.error.handler; 5 6 7 class HipBitmapFont : HipFont 8 { 9 ///The atlas path is saved inside the class 10 string atlasPath; 11 ///This variable is defined when the atlas is being read 12 string atlasTexturePath; 13 14 ///Use that property to know how many characters was read inside the atlas 15 uint charactersCount; 16 17 private string error; 18 19 20 HipFontKerning kerning; 21 22 bool loadAtlas(string data, string atlasPath) 23 { 24 import hip.util.string; 25 this.atlasPath = atlasPath; 26 27 scope int advanceSpace(string data, int i) 28 { 29 while(i < data.length && (data[i].isWhitespace || data[i] == '=')) 30 i++; 31 return i; 32 } 33 scope int nextToken(string data, int i) 34 { 35 i = advanceSpace(data, i); 36 if(i >= data.length) 37 return -1; 38 else if(data[i] == '"') //Find the '"' 39 { 40 i++; 41 while(i < data.length && data[i] != '"') 42 { 43 if(data[i] == '\\') 44 i++; 45 i++; 46 } 47 if(i < data.length)i++; 48 49 } 50 else if(data[i].isAlpha) 51 { 52 while(i <= data.length && data[i].isAlpha) 53 i++; 54 } 55 else if(data[i].isNumeric) 56 { 57 while(i <= data.length && data[i].isNumeric) 58 i++; 59 } 60 return i; 61 } 62 63 scope int getNextInt(string data, ref int i) 64 { 65 import hip.util.conv; 66 int start = advanceSpace(data, i); 67 i = nextToken(data, start); 68 if(i != -1) 69 { 70 return to!int(data[start..i]); 71 } 72 return i; 73 } 74 scope string getNextString(string data, ref int i) 75 { 76 int start = advanceSpace(data, i); 77 i = nextToken(data, start); 78 if(i != -1) 79 { 80 if(data[start] == '"') 81 return data[start+1..i-1]; 82 else 83 return data[start..i]; 84 } 85 return ""; 86 } 87 string name; 88 int size; 89 90 int bold, italic; 91 string charset; 92 int unicode; 93 int stretchH; 94 int smooth, aa; 95 96 int paddingX, paddingY, paddingW, paddingH; 97 int spacingX, spacingY; 98 int outline; 99 100 //Common 101 int lineHeight, base, scaleW, scaleH, pages, packed, alpha, red, green,blue; 102 //Page 103 int pageId; 104 //chars 105 int count; 106 107 int index = 0; 108 enum Context 109 { 110 info, 111 common, 112 page, 113 chars, 114 kernings, 115 unknown 116 } 117 int context = Context.unknown; 118 string key; 119 while(index != -1 && index < data.length) 120 { 121 key = getNextString(data, index); 122 final switch(context) 123 { 124 case Context.info: 125 { 126 switch(key) 127 { 128 case "face": 129 name = getNextString(data, index); 130 break; 131 case "size": 132 size = getNextInt(data, index); 133 break; 134 case "bold": 135 bold = getNextInt(data, index); 136 break; 137 case "italic": 138 italic = getNextInt(data, index); 139 break; 140 case "charset": 141 charset = getNextString(data, index); 142 break; 143 case "unicode": 144 unicode = getNextInt(data, index); 145 break; 146 case "stretchH": 147 stretchH = getNextInt(data, index); 148 break; 149 case "smooth": 150 smooth = getNextInt(data, index); 151 break; 152 case "aa": 153 aa = getNextInt(data, index); 154 break; 155 case "padding": 156 paddingX = getNextInt(data, index); 157 index++; 158 paddingY = getNextInt(data, index); 159 index++; 160 paddingW = getNextInt(data, index); 161 index++; 162 paddingH = getNextInt(data, index); 163 index++; 164 break; 165 case "spacing": 166 spacingX = getNextInt(data, index); 167 index++; 168 spacingY = getNextInt(data, index); 169 index++; 170 break; 171 case "outline": 172 outline = getNextInt(data, index); 173 break; 174 default: 175 goto checkUnknown; 176 } 177 break; 178 } 179 case Context.common: 180 { 181 switch(key) 182 { 183 case "lineHeight": 184 lineHeight = getNextInt(data, index); 185 break; 186 case "base": 187 base = getNextInt(data, index); 188 break; 189 case "scaleW": 190 scaleW = getNextInt(data, index); 191 break; 192 case "scaleH": 193 scaleH = getNextInt(data, index); 194 break; 195 case "pages": 196 pages = getNextInt(data, index); 197 break; 198 case "packed": 199 packed = getNextInt(data, index); 200 break; 201 case "alphaChnl": 202 alpha = getNextInt(data, index); 203 break; 204 case "redChnl": 205 red = getNextInt(data, index); 206 break; 207 case "greenChnl": 208 green = getNextInt(data, index); 209 break; 210 case"blueChnl": 211 blue = getNextInt(data, index); 212 break; 213 default: 214 goto checkUnknown; 215 } 216 break; 217 } 218 case Context.page: 219 { 220 switch(key) 221 { 222 case "id": 223 pageId = getNextInt(data, index); 224 break; 225 case "file": 226 atlasTexturePath = getNextString(data, index); 227 break; 228 default: 229 goto checkUnknown; 230 } 231 break; 232 } 233 case Context.chars: 234 { 235 //Advance "count" 236 charactersCount = count = getNextInt(data, index); 237 uint maxWidth = 0; 238 for(int i = 0; i < count; i++) 239 { 240 HipFontChar ch; 241 //Advance "char" 242 index = nextToken(data, index); 243 //id 244 index = nextToken(data, index); 245 ch.id = getNextInt(data, index); 246 // x 247 index = nextToken(data, index); 248 ch.x = getNextInt(data, index); 249 // y 250 index = nextToken(data, index); 251 ch.y = getNextInt(data, index); 252 // width 253 index = nextToken(data, index); 254 ch.width = getNextInt(data, index); 255 if(ch.width > maxWidth) 256 maxWidth = ch.width; 257 // height 258 index = nextToken(data, index); 259 ch.height = getNextInt(data, index); 260 // xoffset 261 index = nextToken(data, index); 262 ch.xoffset = getNextInt(data, index); 263 // yoffset 264 index = nextToken(data, index); 265 ch.yoffset = getNextInt(data, index); 266 // xadvance 267 index = nextToken(data, index); 268 ch.xadvance = getNextInt(data, index); 269 // page 270 index = nextToken(data, index); 271 ch.page = getNextInt(data, index); 272 // chnl 273 index = nextToken(data, index); 274 ch.chnl = getNextInt(data, index); 275 276 characters[ch.id] = ch; 277 } 278 auto space = ' ' in characters; 279 if(space is null || (space.width == 0 && space.xadvance == 0)) 280 spaceWidth = maxWidth; 281 else 282 spaceWidth = space.xadvance > space.width ? space.xadvance : space.width; 283 lineBreakHeight = lineHeight; 284 context = Context.unknown; 285 break; 286 } 287 case Context.kernings: 288 { 289 //Advance "count" 290 index = nextToken(data, index); 291 int kerningCount = getNextInt(data, index); 292 for(int i = 0; i < kerningCount; i++) 293 { 294 //Advance "kerning " 295 index = nextToken(data, index); 296 297 //first 298 index = nextToken(data, index); 299 int first = getNextInt(data, index); 300 //second 301 index = nextToken(data, index); 302 int second = getNextInt(data, index); 303 //amount 304 index = nextToken(data, index); 305 int amount = getNextInt(data, index); 306 307 if((first in kerning) is null) 308 kerning[first] = HipCharKerning.init; 309 kerning[first][second] = amount; 310 } 311 break; 312 } 313 //Tries to find the context 314 checkUnknown: case Context.unknown: 315 contextSwitch: switch(key) 316 { 317 static foreach(mem; __traits(allMembers, Context)) 318 { 319 case mem: 320 context = __traits(getMember, Context, mem); 321 break contextSwitch; 322 } 323 default: 324 assert(false, "Unknown key received: "~key); 325 } 326 continue; 327 } 328 } 329 330 331 return true; 332 } 333 334 bool loadTexture(IHipTexture t) 335 { 336 texture = t; 337 int width = t.getWidth; 338 int height = t.getHeight; 339 if(width == 0 || height == 0) 340 return false; 341 342 foreach(ref ch; characters) 343 { 344 if(ch.id != 0) 345 { 346 ch.normalizedX = cast(float)ch.x/width; 347 ch.normalizedY = cast(float)ch.y/height; 348 ch.normalizedWidth = cast(float)ch.width/width; 349 ch.normalizedHeight = cast(float)ch.height/height; 350 } 351 } 352 return true; 353 } 354 355 string getTexturePath() 356 { 357 import hip.util.path; 358 string texturePath; 359 if(atlasTexturePath != "") 360 { 361 string atlasDir = atlasPath.dirName; 362 if(atlasDir != atlasPath) 363 texturePath = atlasDir.joinPath(atlasTexturePath); 364 else 365 texturePath = atlasTexturePath; 366 } 367 return texturePath; 368 } 369 370 /** 371 * This won't do anything in case of a bitmap font, as no one can change it. 372 */ 373 override HipFont getFontWithSize(uint size) 374 { 375 HipBitmapFont ret = new HipBitmapFont(); 376 ret.atlasPath = this.atlasPath; 377 ret.atlasTexturePath = this.atlasTexturePath; 378 ret.kerning = cast(HipFontKerning)this.kerning.dup; 379 ret.charactersCount = this.charactersCount; 380 ret._texture = cast(IHipTexture)this._texture; 381 return cast(HipFont)ret; 382 } 383 void readTexture(string texturePath = "") 384 { 385 texturePath = texturePath == "" ? getTexturePath() : texturePath; 386 // auto t = new HipTexture(); 387 // t.load(texturePath); 388 // loadTexture(t); 389 } 390 391 392 static HipBitmapFont fromFile(string atlasPath, string texturePath = "") 393 { 394 auto ret = new HipBitmapFont(); 395 ret.loadAtlas("", atlasPath); 396 ret.readTexture(texturePath); 397 return ret; 398 } 399 400 override int getKerning(const(HipFontChar)* current, const(HipFontChar)* next) const 401 { 402 return getKerning(dchar(current.id), dchar(next.id)); 403 } 404 override int getKerning(dchar current, dchar next) const 405 { 406 const HipCharKerning* chKerning = current in kerning; 407 if(chKerning is null) 408 return 0; 409 const int* kerningValue = next in (*chKerning); 410 if(kerningValue is null) 411 return 0; 412 return *kerningValue; 413 } 414 415 416 }